import { AccessLevel, Inject, SingletonProto } from "@eggjs/tegg";
import fs from 'fs/promises'
import { CryptoUtils } from "../crypto/CryptoUtils";
// import Config from "@/config/Config";
import { HttpException, Success } from "@/core/HttpException";
import { EggLogger } from "typings/app";
import { MapUtils } from "../map/MapUtils";

/**
 * 飞书Token管理工具
 */
@SingletonProto({
    accessLevel: AccessLevel.PUBLIC
})
export class ClientGetTokenUtils {

    @Inject()
    logger: EggLogger

    @Inject()
    eggContextHandler: any

    @Inject()
    cryptoUtils: CryptoUtils

    appConfigCache: Map<string, any> = new Map();

    tokenCache: Map<string, string> = new Map();

    expireCache: Map<string, number> = new Map();

    TokenErrCache: Map<string, number> = new Map();

    TimerCache: Map<string, NodeJS.Timeout> = new Map();

    TimerInterCache: Map<string, NodeJS.Timeout> = new Map();

    appId2Name: Map<string, any> = new Map()
        .set('app_id', 'xx系统')

    /**
     * 初始化
     */
    async init() {
        await this.GetFbToken()
    }

    GetAllTokens() {
        if (this.tokenCache.size != 0) {
            let tokenObj = MapUtils.strMapToObj(this.tokenCache);
            let tokens = {} as any;
            for (const key in tokenObj) {
                if (Object.prototype.hasOwnProperty.call(tokenObj, key)) {
                    if (this.appId2Name.has(key)) {
                        tokens[key] = {
                            key: key,
                            name: this.appId2Name.get(key)
                        }
                    }
                }
            }
            return new Success(tokens)
        } else {
            return new HttpException({}, "服务中缓存飞书Token个数为0！");
        }
    }

    async DeleteAppId(appId: string) {
        await fs.readFile("E:/ExportFkApi" + "/ClientConfigPage.json", { encoding: "utf-8" }).then(async (_res) => {
            let dt = JSON.parse(_res) ?? [];
            let appM = Object.keys(dt['MessageConnect']).filter(key => key !== appId).reduce((result, key) => {
                result[key] = dt['MessageConnect'][key];
                return result;
            }, {});
            dt['MessageConnect'] = appM
            await fs.writeFile("E:/ExportFkApi" + "/ClientConfigPage.json", JSON.stringify(dt), { encoding: "utf-8" }).then(() => {
                this.logger.info("写入成功")
            }).catch((err) => {
                this.logger.error("写入失败", err)
            })
            if (this.appConfigCache.has(appId)) {
                this.appConfigCache.delete(appId);
            }
            if (this.tokenCache.has(appId)) {
                this.tokenCache.delete(appId)
            }
            if (this.expireCache.has(appId)) {
                this.expireCache.delete(appId)
            }
            if (this.TokenErrCache.has(appId)) {
                this.TokenErrCache.delete(appId)
            }
            if (this.TimerCache.has(appId)) {
                clearTimeout(this.TimerCache.get(appId));
                this.TimerCache.set(appId, null!)
                this.TimerCache.delete(appId);
            }
            if (this.TimerInterCache.has(appId)) {
                clearInterval(this.TimerInterCache.get(appId));
                this.TimerInterCache.set(appId, null!)
                this.TimerInterCache.delete(appId);
            }
        })
    }

    async InsertAppId(appId: string, appSecret: string) {
        // appId = this.cryptoUtils.decrypt(appId, Config.SECURITY.SECRET_KEY);
        // appSecret = this.cryptoUtils.decrypt(appSecret, Config.SECURITY.SECRET_KEY);
        await fs.readFile("E:/ExportFkApi" + "/ClientConfigPage.json", { encoding: "utf-8" }).then(async (_res) => {
            let dt = JSON.parse(_res) ?? [];
            let appM = {
                "app_id": appId,
                "app_secret": appSecret
            }
            dt['MessageConnect'][appId] = appM
            await fs.writeFile("E:/ExportFkApi" + "/ClientConfigPage.json", JSON.stringify(dt), { encoding: "utf-8" }).then(() => {
                this.logger.info("写入成功")
            }).catch((err) => {
                this.logger.error("写入失败", err)
            })
        })
        await this.GetFbToken();
    }

    /**
     * 获取飞书Token
     */
    async GetFbToken(appKeyId?: string) {
        if (appKeyId) {
            await this.PostValue(appKeyId);
        } else {
            // 读取配置文件 批量循环计算配置应用
            await fs.readFile("E:/ExportFkApi" + "/ClientConfigPage.json", { encoding: "utf-8" }).then((_res) => {
                let configList = JSON.parse(_res)["MessageConnect"]
                for (const listkey in configList) {
                    if (Object.prototype.hasOwnProperty.call(configList, listkey)) {
                        const config = configList[listkey];
                        this.appConfigCache.set(config.app_id, config)
                    }
                }
            })

            // 循环配置应用 获取token
            for (const [appKey] of this.appConfigCache) {
                await this.PostValue(appKey)
            }
        }
    }

    async PostValue(appKey: string) {
        let appValue: any;
        if (this.appConfigCache.has(appKey)) {
            appValue = this.appConfigCache.get(appKey);
            await this.eggContextHandler.app.axios.post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", appValue).then(async (_res: any) => {
                if (_res.code === 0) {
                    this.logger.info(appKey, _res.expire);
                    this.tokenCache.set(appKey, 'Bearer ' + _res.tenant_access_token);
                    await this.startCountdown(_res.expire * 1000, appKey);
                    this.TokenErrCache.set(appKey, 0);
                    if (this.TimerCache.has(appKey)) {
                        clearTimeout(this.TimerCache.get(appKey))
                        this.TimerCache.set(appKey, null!)
                        this.TimerCache.delete(appKey)
                    }
                } else {
                    if (this.TokenErrCache.has(appKey)) {
                        if (this.TokenErrCache.get(appKey)! >= 3) {
                            this.logger.error(appKey, "错误三次，五分钟后再度启动")
                            if (this.TimerCache.has(appKey)) {
                                clearTimeout(this.TimerCache.get(appKey))
                                this.TimerCache.set(appKey, null!)
                                this.TimerCache.delete(appKey)
                            }
                            this.TimerCache.set(appKey, setTimeout(async () => {
                                await this.GetFbToken(appKey)
                            }, 1000 * 5))
                        }
                    }
                    this.logger.error(appKey, "错误一次，五秒后再度启动")
                    let toerr = Number(this.TokenErrCache.get(appKey) ?? 0)
                    this.TokenErrCache.set(appKey, toerr++)
                    this.tokenCache.delete(appKey);
                    this.expireCache.delete(appKey);
                    if (this.TimerCache.has(appKey)) {
                        clearTimeout(this.TimerCache.get(appKey))
                        this.TimerCache.set(appKey, null!)
                        this.TimerCache.delete(appKey)
                    }
                    this.TimerCache.set(appKey, setTimeout(async () => {
                        await this.GetFbToken(appKey)
                    }, 1000 * 5))
                }
            })
        }
    }

    /**
     * 倒计时
     * @param duration 计时
     * @param appId appId
     */
    async startCountdown(duration: number, appId: string) {
        const endTime = Date.now() + duration;
        if (this.TimerInterCache.has(appId)) {
            clearInterval(this.TimerInterCache.get(appId));
            this.TimerInterCache.set(appId, null!)
            this.TimerInterCache.delete(appId)
            this.tokenCache.delete(appId);
            this.expireCache.delete(appId);
            await this.GetFbToken(appId);
        }
        else {
            this.TimerInterCache.set(appId, setInterval(async () => {
                const timeLeft = endTime - Date.now();
                if (timeLeft < 5 * 60 * 1000) {
                    clearInterval(this.TimerInterCache.get(appId));
                    this.TimerInterCache.set(appId, null!)
                    this.TimerInterCache.delete(appId);
                    this.tokenCache.delete(appId);
                    this.expireCache.delete(appId);
                    await this.GetFbToken(appId);
                }
                this.expireCache.set(appId, Math.floor(timeLeft / 1000))
            }, 1000))
        }
    }

    /**
     * 获取对应Token
     * @param appId appId
     * @returns 
     */
    GetToken(decrypted: string) {
        let appId = decrypted;
        // let appId = this.cryptoUtils.decrypt(decrypted, Config.SECURITY.SECRET_KEY);
        if (this.tokenCache.size != 0) {
            if (this.tokenCache.has(appId)) {
                return new Success({ appToken: this.tokenCache.get(appId), expire: this.expireCache.get(appId) })
            } else {
                return new HttpException("", "获取Token为缓存！");
            }
        } else {
            return new HttpException("", "服务中缓存飞书Token个数为0！");
        }
    }

    /**
     * 内部调用获取Token
     * @param appId appId
     * @returns 
     */
    async InsideToken(appId: string): Promise<string> {
        return await new Promise((resolve, reject) => {
            if (this.tokenCache.size != 0) {
                if (this.tokenCache.has(appId)) {
                    resolve(this.tokenCache.get(appId)!);
                } else {
                    reject("获取Token未缓存！");
                }
            } else {
                reject("服务中缓存飞书Token个数为0！");
            }
        })
    }
}